package com.jiao.autoconfig.config;

import cn.hutool.core.util.ClassUtil;
import com.jiao.comm.utils.ClassUtills;
import com.jiao.datasource.proxy.DataSourceProxy;
import lombok.Setter;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.io.Closeable;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Array;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Logger;

/**
 * @Description
 * @Author Vincent.jiao
 * @Date 2022/5/16 14:37
 */
public class CacheDataSource implements DataSource, Closeable {
    //数据源代理
    private DataSource dataSourceProxy;

    private DataSource dataSource;

    public Map<String, Method> methodMap = new HashMap<>();

    public CacheDataSource(String dataSourceType) throws ClassNotFoundException {
        init(dataSourceType);
    }

    public CacheDataSource(Class dataSourceClass) throws ClassNotFoundException {
        init(dataSourceClass);
    }

    private void init(String dataSourceType) throws ClassNotFoundException {
        ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
        Class dataSourceClass = classLoader.loadClass(dataSourceType);
        init(dataSourceClass);
    }

    private void init(Class dataSourceClass) {
        this.dataSource = (DataSource) BeanUtils.instantiateClass(dataSourceClass);
        dataSourceProxy = getProxyDataSource(dataSource);
    }


    @Override
    public Connection getConnection() throws SQLException {
        return dataSourceProxy.getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return dataSourceProxy.getConnection(username, password);
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return dataSourceProxy.unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return dataSourceProxy.isWrapperFor(iface);
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return dataSourceProxy.getLogWriter();
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
        dataSourceProxy.setLogWriter(out);
    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        dataSourceProxy.setLoginTimeout(seconds);
    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return dataSourceProxy.getLoginTimeout();
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return dataSourceProxy.getParentLogger();
    }

    /**
     * 返回代理数据源.
     * @param dataSource
     * @return
     */
    private DataSource getProxyDataSource (DataSource dataSource) {
        if (dataSource == null) {
            return null;
        }

        //如果已经代理过就不代理，否则代理内部会出现N次调用
        if (dataSource instanceof Proxy) {
            return dataSource;
        }

        InvocationHandler dataSourceProxy = new DataSourceProxy(dataSource);
        DataSource dataSourceProxyObj =  (DataSource) Proxy.newProxyInstance(
                dataSourceProxy.getClass().getClassLoader(), new Class[]{DataSource.class}, dataSourceProxy);

        return dataSourceProxyObj;
    }

    @SneakyThrows
    @Override
    public void close() throws IOException {
//        getMethod("close", null).invoke(dataSourceProxy, null);
        invokeDataSourceTarget();
    }

    private Method getMethod(String methodName, Class... args){
        Method method = methodMap.get(methodName);
        if(method == null) {
            method = ClassUtil.getDeclaredMethod(dataSource.getClass(), methodName, args);
            synchronized (CacheDataSource.class) {
                methodMap.put(methodName, method);
            }
        }

        return method;
    }

    //TODO 待优化
    @SneakyThrows
    public boolean invokeDataSourceTarget(Object... args) {
        StackTraceElement[] stackTraceElement = Thread.currentThread().getStackTrace();
        Class clazz = CacheDataSource.class;

        String methodName = stackTraceElement[2].getMethodName();
        List<Method> methods = ClassUtills.getMethodList(clazz, methodName);
        if(methods.size() > 1) {
            return false;
        }

        Method targetMethod = getMethod(methodName, methods.get(0).getParameterTypes());
        targetMethod.invoke(dataSource, args);
        return true;
    }

    // Properties changeable at runtime through the HikariConfigMXBean
    private volatile String catalog;
    private volatile long connectionTimeout;
    private volatile long validationTimeout;
    private volatile long idleTimeout;
    private volatile long leakDetectionThreshold;
    private volatile long maxLifetime;
    private volatile int maxPoolSize;
    private volatile int minIdle;
    private volatile String username;
    private volatile String password;

    // Properties NOT changeable at runtime
    private long initializationFailTimeout;
    private String connectionInitSql;
    private String connectionTestQuery;
    private String dataSourceClassName;
    private String dataSourceJndiName;
    private String driverClassName;
    private String exceptionOverrideClassName;
    private String jdbcUrl;
    private String poolName;
    private String schema;
    private String transactionIsolationName;
    private boolean isAutoCommit;
    private boolean isReadOnly;
    private boolean isIsolateInternalQueries;
    private boolean isRegisterMbeans;
    private boolean isAllowPoolSuspension;
    private Properties dataSourceProperties;
    private ThreadFactory threadFactory;
    private ScheduledExecutorService scheduledExecutor;
    private Object metricRegistry;
    private Object healthCheckRegistry;
    private Properties healthCheckProperties;
    private long keepaliveTime;
    private volatile boolean sealed;

    @SneakyThrows
    public void setCatalog(String catalog) {
//        getMethod("setCatalog", String.class).invoke(dataSourceProxy, catalog);
        invokeDataSourceTarget(catalog);
    }

    public void setConnectionTimeout(long connectionTimeout) {
        invokeDataSourceTarget(connectionTimeout);
    }

    public void setValidationTimeout(long validationTimeout) {
        invokeDataSourceTarget(validationTimeout);
    }

    public void setIdleTimeout(long idleTimeout) {
        invokeDataSourceTarget(idleTimeout);
    }

    public void setLeakDetectionThreshold(long leakDetectionThreshold) {
        invokeDataSourceTarget(leakDetectionThreshold);
    }

    public void setMaxLifetime(long maxLifetime) {
        invokeDataSourceTarget(maxLifetime);
    }

    public void setMaxPoolSize(int maxPoolSize) {
        invokeDataSourceTarget(maxPoolSize);
    }

    public void setMinIdle(int minIdle) {
        invokeDataSourceTarget(minIdle);
    }

    @SneakyThrows
    public void setUsername(String username) {
//        getMethod("setUsername", String.class).invoke(dataSourceProxy, username);
        invokeDataSourceTarget(username);
    }

    public void setPassword(String password) {
        invokeDataSourceTarget(password);
    }

    public void setInitializationFailTimeout(long initializationFailTimeout) {
//        this.initializationFailTimeout = initializationFailTimeout;
        invokeDataSourceTarget(initializationFailTimeout);
    }

    public void setConnectionInitSql(String connectionInitSql) {
        invokeDataSourceTarget(connectionInitSql);
    }

    public void setConnectionTestQuery(String connectionTestQuery) {
        invokeDataSourceTarget(connectionTestQuery);
    }

    public void setDataSourceClassName(String dataSourceClassName) {
        invokeDataSourceTarget(dataSourceClassName);
    }

    public void setDataSourceJndiName(String dataSourceJndiName) {
        invokeDataSourceTarget(dataSourceJndiName);
    }

    public void setDriverClassName(String driverClassName) {
        invokeDataSourceTarget(driverClassName);
    }

    public void setExceptionOverrideClassName(String exceptionOverrideClassName) {
        invokeDataSourceTarget(exceptionOverrideClassName);
    }

    @SneakyThrows
    public void setJdbcUrl(String jdbcUrl) {
        ClassUtil.getDeclaredMethod(dataSource.getClass(), "setUrl", String.class)
                .invoke(dataSource, jdbcUrl);
    }

    public void setPoolName(String poolName) {
        invokeDataSourceTarget(poolName);
    }

    public void setSchema(String schema) {
        invokeDataSourceTarget(schema);
    }

    public void setTransactionIsolationName(String transactionIsolationName) {
        invokeDataSourceTarget(transactionIsolationName);
    }

    public void setAutoCommit(boolean autoCommit) {
        invokeDataSourceTarget(autoCommit);
    }

    public void setReadOnly(boolean readOnly) {
        invokeDataSourceTarget(readOnly);
    }

    public void setIsolateInternalQueries(boolean isolateInternalQueries) {
        invokeDataSourceTarget(isolateInternalQueries);
    }

    public void setRegisterMbeans(boolean registerMbeans) {
        invokeDataSourceTarget(registerMbeans);
    }

    public void setAllowPoolSuspension(boolean allowPoolSuspension) {
        invokeDataSourceTarget(allowPoolSuspension);
    }

    public void setDataSource(DataSource dataSource) {
        invokeDataSourceTarget(dataSource);
    }

    public void setDataSourceProperties(Properties dataSourceProperties) {
        invokeDataSourceTarget(dataSourceProperties);
    }

    public void setThreadFactory(ThreadFactory threadFactory) {
        invokeDataSourceTarget(threadFactory);
    }

    public void setScheduledExecutor(ScheduledExecutorService scheduledExecutor) {
        invokeDataSourceTarget(scheduledExecutor);
    }

    public void setMetricRegistry(Object metricRegistry) {
        invokeDataSourceTarget(metricRegistry);
    }

    public void setHealthCheckRegistry(Object healthCheckRegistry) {
        invokeDataSourceTarget(healthCheckRegistry);
    }

    public void setHealthCheckProperties(Properties healthCheckProperties) {
        invokeDataSourceTarget(healthCheckProperties);
    }

    public void setKeepaliveTime(long keepaliveTime) {
        invokeDataSourceTarget(keepaliveTime);
    }

    public void setSealed(boolean sealed) {
        invokeDataSourceTarget(sealed);
    }
}
